package edu.cmu.cs211.exprtree.tests;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import edu.cmu.cs211.exprtree.ast.BigDecimalUtils;
import edu.cmu.cs211.exprtree.ast.BinaryExpression;
import edu.cmu.cs211.exprtree.ast.ConstantExpression;
import edu.cmu.cs211.exprtree.ast.Expression;
import edu.cmu.cs211.exprtree.ast.UnaryExpression;
import edu.cmu.cs211.exprtree.ast.VariableExpression;
import edu.cmu.cs211.exprtree.ast.util.Parser;

public class ExpressionTestUtil {

	private static int RANDOM_ITERATIONS = 5;

	// arbitrary values
	private static int DIFFERENT_VALUES = 14;

	private static int OFFSET = 2;
	
	protected static class MockConstantExpression implements Expression {
		private int i;
		public MockConstantExpression(int i) {
			this.i = i;
		}

		public Expression cfold() {
			return new ConstantExpression(i);
		}

		public BigDecimal evaluate(Map<String, BigDecimal> m) {
			return new BigDecimal(i);
		}
	}
	
	public static void cfoldtest(String expr, String expected) {
		/* Parse the input and expected expressions */
		Expression in = Parser.parseExpression(expr);
		Expression actual = in.cfold();
		Expression exp = Parser.parseExpression(expected);

		/* construct the set of variables in the expression */
		Set<String> vars = new HashSet<String>();
		Random r = new Random();
		getVariables(vars, in);

		/* randomly assign values to the variables and test to see if the cfolded expression
		 * and the regular expression return the same values
		 */
		for (int i = 0; i < RANDOM_ITERATIONS; i++) {
			
			Map<String, BigDecimal> assignments = new HashMap<String, BigDecimal>();

			for (String s : vars) {
				assignments.put(s, new BigDecimal(r.nextInt(DIFFERENT_VALUES) + OFFSET));
			}

			try {
				BigDecimal in_value = in.evaluate(assignments);
				BigDecimal out_value = actual.evaluate(assignments);
				assertTrue("Expressions return same result with variable assignments : "
						+ assignments, BigDecimalUtils.areEqual(in_value, out_value));
			} catch (ArithmeticException e) {
			}
		}

		/* check to see if the cfolded expression and the expected expression are equal */
		assertEquals("Constant folding " + expr, exp, actual);

	}

	/* Note: this method does NOT account for inputs that would throw an exception.
	 * You should account for this in specially designed unit tests.
	 */
	public static void evaluatetest(String input, String expected, Map<String, BigDecimal> varmap) {
		/* Parse the input and expected Strings */
		Expression in = Parser.parseExpression(input);
		Expression expectedExpr = null;
		if (expected != null)
			expectedExpr = Parser.parseExpression(expected);

		/* evaluate the two expressions based on the provided variable map */
		BigDecimal evalval = null, expectval = null;
		evalval = in.evaluate(varmap);
		expectval = expectedExpr.evaluate(varmap);

		/* check the results of the valuations for equality */
		assertTrue("Evaluate " + in + " using variable assigments " + varmap + " gives "
				+ evalval + " but expects " + expectval, BigDecimalUtils.areEqual(expectval, evalval));
	}
	
	/* returns a set of Strings representing the variables in the specified expression */
	private static void getVariables(Set<String> vars, Expression e) {
		if (e instanceof VariableExpression)
			vars.add(((VariableExpression) e).getName());
		else if (e instanceof UnaryExpression)
			getVariables(vars, ((UnaryExpression) e).getOperand());
		else if (e instanceof BinaryExpression) {
			BinaryExpression b = (BinaryExpression) e;
			getVariables(vars, b.getLeft());
			getVariables(vars, b.getRight());
		}
	}
}
